﻿using System;
using System.Net;

namespace PacketRush.Core
{
    /// <summary>
    /// A network packet.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <c>Packet</c> is primarily used to hold data to be transfered across the network between <see cref="PacketRush.Core.NetServer"/> and <see cref="PacketRush.Core.NetClient"/>.
    /// </para>
    /// <para>
    /// Services that implement <see cref="PacketRush.Core.INetService"/> can be used to create and consume network packets.
    /// </para>
    /// 
    /// <para>
    /// Some properties of <c>Packet</c> are encoded into a header, which makes up the first part of the packet data, the actual raw packet data is stored in the <see cref="PacketRush.Core.Packet.Data"/> property of <c>Packet</c>. 
    /// Other properties are used to describe how the networking system should interpret the packet, for instance, the <see cref="PacketRush.Core.Packet.Targets"/> property, which applies to <c>NetServer</c> only, 
    /// offers the ability to specify which clients should receive the packet.
    /// </para>
    /// <para>
    /// To summarize, <c>Packet</c> is a helper class for writing to an underlying array of bytes that is the raw packet, other properties of <c>Packet</c> that are not written to the underlying raw packet,
    /// are used to indicate how the <c>Packet</c> should be handled by the system.
    /// </para>
    /// <para>
    /// Packet data is typically written with <see cref="PacketRush.Core.PacketWriter"/> and read with <see cref="PacketRush.Core.PacketReader"/>.
    /// </para>
    /// <para>The following table describes the structure of a packet.</para>
    /// 
    /// <list type="table">
    /// <listheader>
    /// <term>Bytes</term><term>Type</term><term>Description</term>
    /// </listheader>
    /// <item>
    /// <description>0-3</description><description>int</description><description>Length of the packet</description>
    /// </item>
    /// <item>
    /// <description>4-7</description><description>int</description><description>Sequence Index</description>
    /// </item>
    /// <item>
    /// <description>8</description><description>sbyte</description><description>Service ID</description>
    /// </item>
    /// <item>
    /// <description>9-10</description><description>short</description><description>Target ID</description>
    /// </item>
    /// <item>
    /// <description>11-12</description><description>short</description><description>Sender ID</description>
    /// </item>
    /// <item>
    /// <description>13</description><description>byte</description><description>PacketFlags</description>
    /// </item>
    /// <item>
    /// <description>14-x</description><description>byte[]</description><description>Data</description>
    /// </item>
    /// </list>
    /// </remarks>
    public class Packet
    {
        /// <summary>
        /// The maximum size of a packet.
        /// </summary>
        public static readonly int MaxPacketSize = 2048;

        /// <summary>
        /// Describes how many bytes make up the packet header.
        /// </summary>
        public static readonly int HeaderLengthInBytes = 14;

        private uint _SequenceIndex;
        /// <summary>
        /// The sequence number.
        /// </summary>
        /// <remarks>
        /// Sequence numbers are used to identify the packet and enforce reliability.
        /// </remarks>
        public uint SequenceIndex
        {
            get
            {
                return _SequenceIndex;
            }
            set
            {
                WriteSequenceIndex(value, Data);
                _SequenceIndex = value;
            }
        }

        private int _ServiceId;
        /// <summary>
        /// The id of the service that this packet is destined for.
        /// </summary>
        public int ServiceId
        {
            get
            {
                return _ServiceId;
            }
            set
            {
                WriteServiceId(value, Data);
                _ServiceId = value;
            }
        }

        private int _TargetId;
        /// <summary>
        /// Identifies which connection this packet may or may not be sent to.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <see cref="PacketRush.Core.NetServer"/> will use this property to determine where to send the packet, it should be used in conjunction with 
        /// <see cref="PacketRush.Core.Packet.SendToAllButTarget"/>. When this property is set to true, the packet is sent to all connected clients except
        /// the client with a connection id that matches the value of <c>TargetId</c>, when false the packet is sent to just the client specified for <c>TargetId</c>.
        /// </para>
        /// </remarks>
        public int TargetId
        {
            get
            {
                return _TargetId;
            }
            set
            {
                WriteTargetId(value, Data);
                _TargetId = value;
            }
        }

        private int _SenderId;
        /// <summary>
        /// The connection id this packet came from.
        /// </summary>
        /// <remarks>
        /// The value of this property is resolved by <see cref="PacketRush.Core.NetServer"/>, when <c>NetServer</c> receives a packet, it looks up the sender id by end point and writes it to this
        /// property before passing the packet up to the service layer.
        /// </remarks>
        public int SenderId
        {
            get
            {
                return _SenderId;
            }
            set
            {
                WriteSenderId(value, Data);
                _SenderId = value;
            }
        }

        /// <summary>
        /// Identifies a set of clients this packet will be sent to.
        /// </summary>
        /// <remarks>
        /// <para>
        /// A target corresponds to a connection id, when an array of target connection id's are given, <see cref="PacketRush.Core.NetServer"/> 
        /// will send this packet only to the targets given.
        /// </para>
        /// </remarks>
        public int[] Targets { get; set; }


        private PacketFlags _PacketFlags;
        /// <summary>
        /// Describes options for this packet.
        /// </summary>
        public PacketFlags PacketFlags
        {
            get
            {
                return _PacketFlags;
            }
            set
            {
                WritePacketFlags(value, Data);
                _PacketFlags = value;
            }
        }

        /// <summary>
        /// This packet as bytes.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The raw data that makes up this packet which consists of a header followed by arbitrary data.
        /// </para>
        /// </remarks>
        public byte[] Data { get; private set; }

        /// <summary>
        /// Indicates how <c>TargetId</c> is interpreted.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If true then the packet will be sent to all but the target specified with the TargetId property.
        /// If false, then the packet will be sent just to the target as specified with the TargetId property.
        /// </para>
        /// <para>
        /// See <see cref="PacketRush.Core.Packet.TargetId"/> for further information.
        /// </para>
        /// </remarks>
        public bool SendToAllButTarget { get; set; }

        /// <summary>
        /// Create a new Packet.
        /// </summary>
        /// <param name="rawPacket">A raw packet</param>
        protected Packet(byte[] rawPacket) 
        {
            Data = rawPacket;

            _SequenceIndex = Packet.ReadSequenceIndex(rawPacket);
            _ServiceId = Packet.ReadServiceId(rawPacket);
            _TargetId = BitConverter.ToInt16(rawPacket, 9);
            _SenderId = BitConverter.ToInt16(rawPacket, 11);
            _PacketFlags = Packet.ReadPacketFlags(rawPacket);
        }

        /// <summary>
        /// Create a new packet.
        /// </summary>
        protected Packet() { }

        /// <summary>
        /// Create a new packet.
        /// </summary>
        /// <param name="rawPacket">The raw packet to create the packet from.</param>
        /// <returns>A instance of <c>Packet</c>.</returns>
        public static Packet Create(byte[] rawPacket)
        {
            Packet packet = new Packet(rawPacket);
            return packet;
        }

        /// <summary>
        /// Writes the sequence index to the given rawpacket.
        /// </summary>
        /// <param name="sequenceIndex">The sequence index to write.</param>
        /// <param name="rawPacket">The raw packet to write to.</param>
        public static void WriteSequenceIndex(uint sequenceIndex, byte[] rawPacket)
        {
            Buffer.BlockCopy(BitConverter.GetBytes(sequenceIndex), 0, rawPacket, 4, 4);
        }

        /// <summary>
        /// Writes the service id of the service that handles this packet to the given raw packet.
        /// </summary>
        /// <param name="serviceId">The service id of the service to write.</param>
        /// <param name="rawPacket">The raw packet to write to.</param>
        public static void WriteServiceId(int serviceId, byte[] rawPacket)
        {
            Buffer.BlockCopy(BitConverter.GetBytes((short)serviceId), 0, rawPacket, 8, 1);
        }

        /// <summary>
        /// Writes the target id to the given raw packet.
        /// </summary>
        /// <param name="targetId">The target id to write.</param>
        /// <param name="rawPacket">The raw packet to write to.</param>
        public static void WriteTargetId(int targetId, byte[] rawPacket)
        {
            Buffer.BlockCopy(BitConverter.GetBytes((short)targetId), 0, rawPacket, 9, 2);
        }

        /// <summary>
        /// Writes the sender id to the given raw packet.
        /// </summary>
        /// <param name="senderId">The sender id to write.</param>
        /// <param name="rawPacket">The raw packet to write to.</param>
        public static void WriteSenderId(int senderId, byte[] rawPacket)
        {
            Buffer.BlockCopy(BitConverter.GetBytes((short)senderId), 0, rawPacket, 11, 2);
        }

        /// <summary>
        /// Writes the <c>PacketFlags</c> to the given raw packet.
        /// </summary>
        /// <param name="packetFlags">The <c>PacketFlags</c> to write.</param>
        /// <param name="rawPacket">The raw packet to write to.</param>
        public static void WritePacketFlags(PacketFlags packetFlags, byte[] rawPacket)
        {
            Buffer.BlockCopy(BitConverter.GetBytes((byte)packetFlags), 0, rawPacket, 13, 1);
        }

        /// <summary>
        /// Reads the sequence index from the given raw packet.
        /// </summary>
        /// <param name="rawPacket">The raw packet to read from.</param>
        /// <returns>The sequence index.</returns>
        public static uint ReadSequenceIndex(byte[] rawPacket)
        {
            return BitConverter.ToUInt32(rawPacket, 4);
        }

        /// <summary>
        /// Reads the service id from the given raw packet.
        /// </summary>
        /// <param name="rawPacket">The raw packet to read from.</param>
        /// <returns>The service id.</returns>
        public static sbyte ReadServiceId(byte[] rawPacket)
        {
            return (sbyte) rawPacket[8];
        }

        /// <summary>
        /// Reads the <c>PacketFlags</c> from the given raw packet.
        /// </summary>
        /// <param name="rawPacket">The raw packet to read from.</param>
        /// <returns>The <c>PacketFlags</c>.</returns>
        public static PacketFlags ReadPacketFlags(byte[] rawPacket)
        {
            return (PacketFlags) rawPacket[13];
        }

        /// <summary>
        /// Converts a <c>Packet</c> to a raw packet.
        /// </summary>
        /// <param name="packet">The <c>Packet</c> to convert.</param>
        /// <returns>A raw packet.</returns>
        public static implicit operator byte[](Packet packet)
        {
            return packet.Data;
        }

        /// <summary>
        /// Converts a raw packet to a <c>Packet</c>.
        /// </summary>
        /// <param name="rawPacket">The raw packet to convert.</param>
        /// <returns>A <c>Packet</c>.</returns>
        public static explicit operator Packet(byte[] rawPacket)
        {
            return new Packet(rawPacket);
        }
    }
}
